﻿using Sunny.FrameDecoder;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using TouchSocket.Core;
using TouchSocket.Sockets;

namespace Sunny.Tcp
{
    public class TcpSocketClient : SocketClient
    {
        public BaseByteFrameDecoder FrameDecoder { get; set; }

        public DateTime ConnectedTime { get; set; }

        public bool PingConnected { get; set; }

        public object Tag { get; set; }

        public string Name { get; set; } = "Id = {Id}, IPAdress = {IP}:{Port}";

        public TcpClientDisconnectedType DisconnectedType { get; set; } = TcpClientDisconnectedType.Disconnected;

        public void ASS()
        {

        }

        /// <summary>
        /// 安全性发送关闭报文
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="client"></param>
        /// <param name="how"></param>
        public bool Shutdown(TcpClientDisconnectedType disconnectedType = TcpClientDisconnectedType.Disconnected, SocketShutdown how = SocketShutdown.Both)
        {
            DisconnectedType = disconnectedType;
            ConnectedTime = DateTime.MinValue;
            PingConnected = false;

            try
            {
                if (!MainSocket.Connected) return false;
                MainSocket?.Shutdown(how);
                return true;
            }
            catch
            {
                return false;
            }
        }
    }

    /// <summary>
    /// TCP服务器
    /// </summary>
    internal class TcpSocketService : TcpService<TcpSocketClient>
    {
        /// <summary>
        /// 处理数据
        /// </summary>
        public ReceivedEventHandler<TcpSocketClient> Received { get; set; }

        protected override Task OnReceived(TcpSocketClient socketClient, ReceivedDataEventArgs e)
        {
            this.Received?.Invoke(socketClient, e);
            return EasyTask.CompletedTask;
        }
    }

    internal class SendDataItem : IDisposable
    {
        public TcpSocketClient Client { get; private set; }

        public ByteBlock Buffer { get; private set; }

        public SendDataItem(TcpSocketClient client, byte[] buffer)
        {
            Client = client;
            Buffer = new ByteBlock(buffer);
        }

        public SendDataItem(byte[] buffer)
        {
            Client = null;
            Buffer = new ByteBlock(buffer);
        }

        public void Dispose()
        {
            Buffer.Dispose();
        }
    }

    /// <summary>
    /// TCP服务端
    /// </summary>
    public class TcpServer : IDisposable
    {
        private readonly TcpSocketService service = new();

        private readonly ConcurrentQueue<SendDataItem> SendBytes = new();

        public bool UseSendThread { get; set; } = true;

        public Func<BaseByteFrameDecoder> FrameDecoder { get; set; }

        private TimeSpan freeClearTimeSpan = TimeSpan.FromSeconds(10);
        /// <summary>
        /// 空闲断开时间间隔（未收到Ping或者数据）
        /// </summary>
        public TimeSpan FreeClearTimeSpan
        {
            get => freeClearTimeSpan;
            set => freeClearTimeSpan = value < TimeSpan.FromSeconds(1) ? TimeSpan.FromSeconds(1) : value;
        }

        public bool FreeClearEnabled { get; set; } = false;

        public int ClientCount => GetClients().Count();

        private string name;

        public bool IsStart => State == ServerState.Running;

        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get => name ?? this.GetType().Name;
            set => name = value;
        }

        /// <summary>
        /// 客户端发送的心跳包
        /// </summary>
        public byte[] Ping { get; set; }


        private TimeSpan pingClearTimeSpan = TimeSpan.FromSeconds(3);
        /// <summary>
        /// 有Ping设置时，连接后未发Ping清理连接客户端时间
        /// </summary>
        public TimeSpan PingClearTimeSpan
        {
            get => pingClearTimeSpan;
            set => pingClearTimeSpan = value < TimeSpan.FromSeconds(2) ? TimeSpan.FromSeconds(2) : value;
        }

        public bool PingClearEnabled { get; set; } = true;

        /// <summary>
        /// 回复客户端的心跳包
        /// </summary>
        public byte[] Pong { get; set; }

        /// <summary>
        /// 监听端口
        /// </summary>
        public int Port { get; private set; }

        public ServerState State => service.ServerState;

        /// <summary>
        /// 标签
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// 清理时间间隔
        /// </summary>
        protected TimeSpan ClearThreadSleepTimeSpan { get; set; } = TimeSpan.FromMilliseconds(500);

        /// <summary>
        /// 发送线程休眠时间
        /// </summary>
        public TimeSpan ThreadSleepTimeSpan { get; set; } = TimeSpan.FromMilliseconds(10);

        public event OnTcpServerClientConnectStateChanged OnConnected;

        public event OnTcpServerClientDisconnectStateChanged OnDisconnected;

        /// <summary>
        /// 错误回调事件
        /// </summary>
        public event OnTcpSocketError OnError;

        /// <summary>
        /// 接收数据事件
        /// </summary>
        public event OnTcpServerByteEventHandler OnReceived;

        /// <summary>
        /// 服务开启事件
        /// </summary>
        public event EventHandler OnStarted;

        /// <summary>
        /// 服务停止事件
        /// </summary>
        public event EventHandler OnStopped;

        protected virtual void DoConnected(TcpServerClientEventArgs e)
        {
            Console.WriteLine(Name + " Connected: " + e.Client.GetIPPort() + ", " + e.ConnectedType);
        }

        protected virtual void DoDisconnected(TcpServerClientDisconnectEventArgs e)
        {
            Console.WriteLine(Name + " Disconnected: " + e.Client.GetIPPort() + ", " + e.DisconnectedType + ", " + e.Message);
        }

        protected virtual void DoError(Exception e)
        {
            Console.WriteLine(Name + " Error: " + e.Message);
        }

        protected virtual void DoRecieved(TcpServerByteDataEventArgs e) { }

        protected virtual void DoStarted()
        {
            Console.WriteLine(Name + " Started.");
        }

        protected virtual void DoStopped()
        {
            Console.WriteLine(Name + " Stopped.");
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">端口</param>
        public TcpServer(int port)
        {
            if (!port.ValidateTcpPort())
            {
                throw new ArgumentException("port");
            }

            Port = port;

            //有客户端成功连接
            service.Connected = (client, e) =>
            {
                client.ConnectedTime = DateTime.Now;

                //没有Ping，直接加到Clients
                if (Ping.NotExists())
                {
                    var args = new TcpServerClientEventArgs(client, TcpClientConnectedType.Connected);
                    DoConnected(args);
                    OnConnected?.Invoke(this, args);
                }

                return EasyTask.CompletedTask;
            };

            //有客户端断开连接
            service.Disconnected = (client, e) =>
            {
                var args = new TcpServerClientDisconnectEventArgs(client, client.DisconnectedType, e.Manual, e.Message);
                DoDisconnected(args);
                OnDisconnected?.Invoke(this, args);
                return EasyTask.CompletedTask;
            };

            service.Received = (client, e) =>
            {
                byte[] data = e.ByteBlock.ToArray();
                bool PingExists = Ping.Exists() && data.AsSpan().IndexOf(Ping) >= 0;

                if (Ping.Exists() && PingExists && !client.PingConnected)
                {
                    client.PingConnected = true;
                    var args = new TcpServerClientEventArgs(client, TcpClientConnectedType.ConnectedByPing);
                    DoConnected(args);
                    OnConnected?.Invoke(this, args);
                }

                if (Ping.Exists() && Pong.Exists() && PingExists)
                {
                    client.Send(Pong);
                }

                if (FrameDecoder == null)
                {
                    var args = new TcpServerByteDataEventArgs(this, client, data);
                    DoRecieved(args);
                    OnReceived?.Invoke(this, args);
                }
                else
                {
                    if (client.FrameDecoder == null)
                    {
                        var decoder = FrameDecoder.Invoke();
                        decoder.IPEndPoint = new System.Net.IPEndPoint(IPAddress.Parse(client.IP), client.Port);
                        decoder.Tag = client;
                        decoder.OnDecoder += Decoder_OnDecoder;
                        client.FrameDecoder = decoder;
                    }

                    client.FrameDecoder.Decode(data);
                }

                return EasyTask.CompletedTask;
            };
        }

        public virtual void Dispose()
        {
            Stop();
            service.Dispose();
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="ClientId">客户端</param>
        /// <param name="data">数据</param>
        public void Send(string ClientId, byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (service.SocketClients.TryGetSocketClient(ClientId, out TcpSocketClient client))
            {
                Send(client, buffer);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="ClientId">客户端</param>
        /// <param name="buffer">数据</param>
        /// <param name="offset">起始位置</param>
        /// <param name="size">长度</param>
        public void Send(string ClientId, byte[] buffer, int offset, int size)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (offset < 0 || offset > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("Index must be greater than or equal to zero.");
            }

            if (size < 0 || size > buffer.Length - offset)
            {
                throw new ArgumentOutOfRangeException("Length must be less than or equal data length.");
            }

            if (service.SocketClients.TryGetSocketClient(ClientId, out TcpSocketClient client))
            {
                Send(client, buffer, offset, size);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="client">客户端</param>
        /// <param name="data">数据</param>
        public void Send(TcpSocketClient client, byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (UseSendThread)
            {
                SendBytes.Enqueue(new SendDataItem(client, buffer));
            }
            else
            {
                TrySend(client, buffer);
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="ClientId">客户端</param>
        /// <param name="buffer">数据</param>
        /// <param name="offset">起始位置</param>
        /// <param name="size">长度</param>
        public void Send(TcpSocketClient client, byte[] buffer, int offset, int size)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (offset < 0 || offset > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("Index must be greater than or equal to zero.");
            }

            if (size < 0 || size > buffer.Length - offset)
            {
                throw new ArgumentOutOfRangeException("Length must be less than or equal data length.");
            }

            Send(client, buffer.AsSpan().Slice(offset, size).ToArray());
        }

        protected void TrySend(TcpSocketClient client, byte[] buffer)
        {
            try
            {
                if (client.CanSend)
                {
                    client.Send(buffer);
                }
            }
            catch (Exception ex)
            {
                DoError(ex);
                OnError?.Invoke(this, ex);
            }
        }

        public void SendToAll(byte[] buffer)
        {
            SendToAll(buffer, 0, buffer.Length);
        }

        public void SendToAll(byte[] buffer, int offset, int size)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("Data cannot be empty.");
            }

            if (offset < 0 || offset > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("Index must be greater than or equal to zero.");
            }

            if (size < 0 || size > buffer.Length - offset)
            {
                throw new ArgumentOutOfRangeException("Length must be less than or equal data length.");
            }

            if (offset == 0 && size == buffer.Length)
            {
                SendBytes.Enqueue(new SendDataItem(buffer));
            }
            else
            {
                SendBytes.Enqueue(new SendDataItem(buffer.AsSpan().Slice(offset, size).ToArray()));
            }
        }

        /// <summary>
        /// 开启接收数据线程
        /// </summary>
        public virtual void Start()
        {
            if (service.ServerState == ServerState.Running) return;

            try
            {
                TouchSocketConfig config = new();
                config.SetListenIPHosts(new IPHost[] { new IPHost(Port) });
                config.SetMaxCount(1000);
                service.Setup(config);
                service.Start();
                DoStarted();
                OnStarted?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                DoError(ex);
                OnError?.Invoke(this, ex);
            }

            if (service.ServerState == ServerState.Running)
            {
                IsThreadStart = true;
                _lastCheckClearTime = DateTime.MinValue;
                _thread = new Thread(OnThread) { IsBackground = true };
                _thread.Start();//开启
            }
        }

        private Thread _thread;
        private DateTime _lastCheckClearTime = DateTime.MinValue;

        /// <summary>
        /// 客户端清理线程是否开启
        /// </summary>
        private bool IsThreadStart { get; set; }

        private void OnThread()
        {
            while (IsThreadStart)
            {
                try
                {
                    while (!SendBytes.IsEmpty)
                    {
                        if (SendBytes.TryDequeue(out var item))
                        {
                            if (item.Client != null)
                            {
                                TrySend(item.Client, item.Buffer.ToArray(0, item.Buffer.Len));
                            }

                            foreach (var client in service.GetClients())
                            {
                                if (Ping.Exists())
                                {
                                    if (client.PingConnected) TrySend(client, item.Buffer.ToArray(0, item.Buffer.Len));
                                }
                                else
                                {
                                    TrySend(client, item.Buffer.ToArray(0, item.Buffer.Len));
                                }
                            }

                            item.Dispose();
                        }
                    }
                }
                catch (Exception ex)
                {
                    DoError(ex);
                    OnError?.Invoke(this, ex);
                }

                if (DateTime.Now - _lastCheckClearTime > ClearThreadSleepTimeSpan)
                {
                    if ((Ping.Exists() && PingClearEnabled) || FreeClearEnabled)
                    {
                        foreach (var Id in service.GetIds())
                        {
                            if (service.SocketClients.TryGetSocketClient(Id, out TcpSocketClient client))
                            {
                                if (Ping.Exists() && PingClearEnabled && !client.PingConnected && DateTime.Now - client.ConnectedTime >= PingClearTimeSpan)
                                {
                                    client.Shutdown(TcpClientDisconnectedType.DisconnectedByPingTimeout);
                                }

                                if (FreeClearEnabled && client.ConnectedTime > DateTime.MinValue && DateTime.Now - client.ConnectedTime > FreeClearTimeSpan && DateTime.Now - client.LastReceivedTime > FreeClearTimeSpan)
                                {
                                    client.Shutdown(TcpClientDisconnectedType.DisconnectedByFreeTimeout);
                                }
                            }
                        }
                    }

                    _lastCheckClearTime = DateTime.Now;
                }

                Thread.Sleep(ThreadSleepTimeSpan);
            }

            while (!SendBytes.IsEmpty)
            {
                SendBytes.TryDequeue(out _);
            }
        }

        /// <summary>
        /// 停止接收数据线程
        /// </summary>
        public virtual void Stop()
        {
            IsThreadStart = false;
            if (IsStart)
            {
                foreach (var client in service.GetClients())
                {
                    client.Shutdown(TcpClientDisconnectedType.DisconnectedByServerStopped);
                }

                service.Stop();
            }

            DoStopped();
            OnStopped?.Invoke(this, EventArgs.Empty);
        }

        private void Decoder_OnDecoder(object sender, IByteEventArgs e)
        {
            var args = new TcpServerByteDataEventArgs(this, (TcpSocketClient)e.Decoder.Tag, e);
            DoRecieved(args);
            OnReceived?.Invoke(this, args);
        }

        public void TryShutdown(TcpSocketClient client, TcpClientDisconnectedType disconnectedType = TcpClientDisconnectedType.DisconnectedByServer, SocketShutdown how = SocketShutdown.Both)
        {
            client.Shutdown(disconnectedType, how);
        }

        public IEnumerable<TcpSocketClient> GetClients()
        {
            if (Ping.NotExists())
                return service.GetClients();
            else
                return service.SocketClients.GetClients().Select(p => (TcpSocketClient)p).Where(p => p.PingConnected);
        }
    }
}
